package main

import (
	"fmt"
	"log"
	"net/http"

	"os"
	"regexp"
	"strconv"
	"strings"
	"time"

	"golang.org/x/net/websocket"
)

type PasswordWS struct {
	Password      string
	WS            *websocket.Conn
	sendUsrString string
}

//连接过的用户数
var i int = 1

//集合 类型[string]*websocket.Conn string 键string类型：值*PasswordWS
//键 string类型 储存唯一值账号
var countryCapitalMap = make(map[string]*PasswordWS)

//为了查询当前地址是什么账号
var IPcountryCapitalMap = make(map[*websocket.Conn]string)

//文件路径
var fileUrlString string = "user_data"

//控制台输出 状态 测试使用 true
var tre bool = true

//用户信息
var reply []byte

//正则 登录
var setloginRegexpString = `setlogin:`

//正则 添加接收人
var addSendUsrRegexpString = `addSendUsr:`

//错误信息
var err error

func fileEcho(ws *websocket.Conn) {
	//正则 登录状态
	var setloginFlysnowRegexp = regexp.MustCompile(setloginRegexpString)
	//正则 添加发送人用户状态
	var addSendUsrFlysnowRegexp = regexp.MustCompile(addSendUsrRegexpString)

	//判断使用
	var usrLoginPasswordWS *PasswordWS
	var ok bool
	var userloginString string

	//测试使用
	//countryCapitalMap[ws] = "默认名"

	//没发送信息就已经打印 说明连接产生 ws
	WriteWithFileWrite(fileUrlString, strconv.Itoa(i)+" :用户连接", tre)

	for {

		err = websocket.Message.Receive(ws, &reply)
		//更新 用户信息

		if err != nil {
			//错误信息不为空，有错误

			WriteWithFileWrite(fileUrlString, strconv.Itoa(i)+" :用户连接关闭", tre)
			//记录信息

			//删除当前地址的账号 登记地址
			if userloginString, ok = IPcountryCapitalMap[ws]; ok {
				//查询当前账号
				if usrLoginPasswordWS, ok = countryCapitalMap[userloginString]; ok {
					//通过账号查询用户信息map
					usrLoginPasswordWS.WS = nil
					// 删除地址
					usrLoginPasswordWS.sendUsrString = ""
				}
			}
			delete(IPcountryCapitalMap, ws)
			// 删除当前在线地址

			return
		}

		/*逻辑：对登录的用户接收人不在线拦截***
		没有登录的用户又没有发送登录操作信息来，后期做
		这里对已经登录的用户，接收人不在线来信息跳过
		***********************
		得到用户信息判断接收人否在线就跳过
		*/
		//当前的地址 当前的账号 接收人信息 接收人的地址
		if userloginString, ok = IPcountryCapitalMap[ws]; ok {
			if lan_usrLoginPasswordWS, ok := countryCapitalMap[userloginString]; ok {
				//说明登录了

				if usrLoginPasswordWS, ok = countryCapitalMap[lan_usrLoginPasswordWS.sendUsrString]; ok {
					//说明设置了接收人，没有 后面可以设置操作
					if usrLoginPasswordWS.WS == nil {
						/*没设置接收人，在发送时会拦截*/
						lan_usrLoginPasswordWS.sendUsrString = ""
						continue
						//登录了也设置了接收人，但是不在线就拦截跳过了
					}
				}
			}

		}

		/*逻辑：添加接收人
		如果是 正则：指定字符串加接收人账号 就下一步
		判断当前的地址账号是否已登录
		进入后添加不了的就当只是添加操作跳过了
		*/
		if addSendUsrFlysnowRegexp.FindStringSubmatch(string(reply)) != nil {

			//三联 位置不可以随便修改
			if userloginString, ok = IPcountryCapitalMap[ws]; !ok {
				//查询当前地址的用户账号
				continue
			}
			if usrLoginPasswordWS, ok = countryCapitalMap[userloginString]; !ok {
				//查询当前账号的用户信息
				continue
			}
			if usrLoginPasswordWS.WS == nil {
				//查询到当前用户信息的登记地址不为空
				continue

			}

			sendUserloginString := strings.Replace(string(reply), addSendUsrRegexpString, "", -1)
			//登录状态信息替换空白,得到目标账号
			usrLoginPasswordWS.sendUsrString = sendUserloginString
			//添加接收人
			// 能进入这里，后面就不需要执行了
			continue
			//
		}
		/*逻辑：登录操作
		是登录操作就 判断账号的存在，不存在退出
		查询账号的登记地址，存在就退出
		最后就认可用户操作，添加 当前用户的地址加账号 在IPcountryCapitalMap
		*/
		if setloginFlysnowRegexp.FindStringSubmatch(string(reply)) != nil {
			//判断是登录操作

			userloginString = strings.Replace(string(reply), setloginRegexpString, "", -1)
			//登录状态信息替换空白,得到账号
			if usrLoginPasswordWS, ok = countryCapitalMap[userloginString]; !ok {
				websocket.Message.Send(ws, "账号不存在")
				WriteWithFileWrite(fileUrlString, userloginString+" :账号不存在", tre)
				return
				//不存在账号退出
			}
			//如果要登录的账号有登记地址就退出
			if usrLoginPasswordWS.WS != nil {
				websocket.Message.Send(ws, "账号已登录")
				WriteWithFileWrite(fileUrlString, userloginString+" :该用户已登录", tre)
				return
			}
			IPcountryCapitalMap[ws] = userloginString
			//存在账号 储存
			continue
			//能进入这里就认为只是登录操作，后面就跳过
		}

		/*逻辑：验证密码操作
		从登录操作，得知当前地址已经储存在IPcountryCapitalMap和要登录的账号
		要登录的账号登记地址还是为空的
		******************************************************************
		通过ws查询当前的IPcountryCapitalMap信息，存在就下一步
		查询登录的账号没有登记地址存在就验证密码
		否则判断登记地址与当前地址一样就认可已经登录
		*/
		if userloginString, ok = IPcountryCapitalMap[ws]; ok {
			//查询当前地址的账号

			/*账号存在才登记的，所以查询一定有数据返回*/
			usrLoginPasswordWS, _ = countryCapitalMap[userloginString]
			//得到当前地址的用户信息
			if usrLoginPasswordWS.WS == nil {
				// 验证当前地址的用户信息没有登记地址
				setlogin(ws, userloginString)
				// 验证密码操作
				/*能进入这里说明就只是登录操作使用，后面的就跳过了*/

				continue

			}

			if usrLoginPasswordWS.WS == ws {
				// 当前地址与登记的地址是否一样

				if usrLoginPasswordWS, ok = countryCapitalMap[usrLoginPasswordWS.sendUsrString]; ok {
					// 查询得出来就准备转发
					usrUse(ws, usrLoginPasswordWS.WS)

					// 登录后 实现转发操作
				}
			}

		}

		// for
	}
	//func
}

//开始函数
func main() {
	//测试使用
	countryCapitalMap["220"] = &PasswordWS{Password: "440"}
	countryCapitalMap["330"] = &PasswordWS{Password: "990"}

	//websocket.Handler(Echo)Echo函数 自定义的
	http.Handle("/", websocket.Handler(fileEcho))
	//监听端口 没有ip默认
	err := http.ListenAndServe("127.0.0.1:8080", nil)
	//失败打印信息
	if err != nil {
		log.Fatal("ListenAndServe:", err)
	}

}

//功能
//用户 验证密码操作
func setlogin(ws *websocket.Conn, userloginString string) {

	// 密码错误退出
	if string(reply) != countryCapitalMap[userloginString].Password {
		websocket.Message.Send(ws, "密码错误")
		WriteWithFileWrite(fileUrlString, string(reply)+" :密码错误", tre)

		return
	}

	//用户信息 登记地址
	countryCapitalMap[userloginString].WS = ws
	//记录信息
	WriteWithFileWrite(fileUrlString, userloginString+" 用户登录成功", tre)

	//发送信息给用户成功：账号+ok
	if err = websocket.Message.Send(ws, userloginString+"ok"); err != nil {
		//证明用户已退出 删除地址
		countryCapitalMap[userloginString].WS = nil
		countryCapitalMap[userloginString].sendUsrString = ""
		// 删除当前在线地址
		delete(IPcountryCapitalMap, ws)

		//记录信息
		WriteWithFileWrite(fileUrlString, userloginString+" 用户退出", tre)

		return
	}

}

//用户登录后 数据转发操作
/*
逻辑：
通过当前地址查询到接收人账号地址
*/
func usrUse(ws *websocket.Conn, sendWS *websocket.Conn) {

	//发送之前确认对方是否在线
	if sendWS == nil {

		websocket.Message.Send(ws, "接收人不在线")
		WriteWithFileWrite(fileUrlString, "接收人不在线", tre)
		return
	}
	// 测试使用
	fmt.Println(reply)
	//实现转发
	if err = websocket.Message.Send(sendWS, reply); err != nil {

		websocket.Message.Send(ws, "数据发送异常")
		WriteWithFileWrite(fileUrlString, string(reply)+"数据发送异常", tre)
	}

}

//	数据写到文件中 并能控制 控制台是否输出
/*文件路径 写入的字符串 是否控制台输出提示信息 */
func WriteWithFileWrite(name, content string, t bool) {
	fileObj, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
	if err != nil {

		控制台输出("打开文件错误:"+err.Error(), t)
		os.Exit(2)
	}
	defer fileObj.Close()
	if _, err := fileObj.WriteString(content + "     时间：" + time.Now().String() + "\n"); err == nil {

		控制台输出("写入成功内容： "+content, t)
	}

}

func 控制台输出(s string, t bool) {
	if t {

		fmt.Println(s)
	}
}
